package com.whiteape.book.core.manager;

import android.util.Base64;

import com.whiteape.book.core.engine.JSEngine;
import com.whiteape.book.core.engine.JsonPathEngine;
import com.whiteape.book.core.engine.JsoupSelectorEngine;
import com.whiteape.book.core.engine.XPathEngine;
import com.whiteape.book.model.bo.MultiRuleBo;
import com.whiteape.book.model.bo.RuleBo;
import com.whiteape.book.model.type.MultiRuleOpType;
import com.whiteape.book.model.type.RuleType;
import com.whiteape.book.utils.GsonUtils;
import com.whiteape.book.utils.StringUtils;

import org.json.JSONArray;
import org.json.JSONObject;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.NativeObject;
import org.seimicrawler.xpath.JXNode;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ParserManager {

    //不支持嵌套，所以用非贪婪匹配?
    static final Pattern DYNAMIC_PARAMS = Pattern.compile("\\{\\{.*?\\}\\}");
    //TODO 现在 list形式的，比如章节list,搜索list，只能有一中规则存在
//    public static void parseList(String doc, String rule) {
//        LinkedList<RuleBo> ruleBoList = analyzeRule(rule);
//        String result = doc;
//        for(RuleBo ruleBo : ruleBoList) {
//            List obj = getElementList(result, ruleBo);
//            result =
//        }
//
//    }

    /**
     * 顺序执行每一个rule，上一条的结果作为下一条的入参
     *
     * @param doc
     * @param rule
     * @return
     */
    public static String parseOne(String url, String doc, String rule, Map<String, String> varMap, boolean isInnerStatement) {
        try {
            if (StringUtils.isEmpty(doc) || StringUtils.isEmpty(rule)) {
                return "";
            }
            LinkedList<RuleBo> ruleBoList = analyzeRule(rule, isInnerStatement);
            String result = doc;
            for (RuleBo ruleBo : ruleBoList) {
                //如果还有&& 或者 ||
                if (ruleBo instanceof MultiRuleBo) {
                    switch (((MultiRuleBo) ruleBo).getOpType()) {
                        case AND:
                            //对于单个rule而言，不应该还有&&, 只有列表的，才有&&来组合多个
                            break;
                        case OR:
                            String subResult = "";
                            for (RuleBo subRuleBo : ((MultiRuleBo) ruleBo).getRuleBoList()) {
                                if (!StringUtils.isEmpty(subResult)) {
                                    break;
                                }
                                subResult = getString(url, result, subRuleBo, varMap);
                            }
                            if (!StringUtils.isEmpty(subResult)) {
                                result = subResult;
                                break;
                            }
                            break;
                    }
                } else {
                    result = getString(url, result, ruleBo, varMap);
                }
            }
            return result == null ? "" : result;
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    public static String parseOne(String url, String doc, String rule, Map<String, String> varMap) {
        return parseOne(url, doc, rule, varMap, false);
    }

//    public static String parseOne(String doc, String rule, Map<String, String> varMap) {
//        return parseOne(null, doc, rule, varMap);
//    }

    /**
     * @param isInnerStatement :是否是内部语句块的解析，也就是在{{}}中的语句规则解析
     *                         在{{}}内的，如果无法推断，就认定是@STR:; 不在{{}}中的，如果无法推断，认定是@CSS
     *                         <p>
     *                         TODO 考虑根据内容推断
     * @JSON:
     * @JS: function getResult(html,url)
     * @XPATH:
     * @CSS:
     * @CSS:a > div.comic-content > img@src|||@JS:asdfsdfdf
     * @JS必须带头 其他的默认 $开头是@JSON, /开头是xpath，都不是的，就是css的
     */
    private static LinkedList<RuleBo> analyzeRule(String rule, boolean isInnerStatement) {
        LinkedList<RuleBo> ruleBoLinkedList = new LinkedList<>();
        if (StringUtils.isEmpty(rule)) {
            return ruleBoLinkedList;
        }
        String[] strArr = rule.split("\\|\\|\\|");
        for (String str : strArr) {
            str = str.trim();

            RuleBo ruleBo = new RuleBo(str);
            if (str.contains("&&")) {
                ruleBo = new MultiRuleBo(str);
                LinkedList<RuleBo> subRuleBoList = new LinkedList<>();
                String[] subRuleArr = str.split("&&");
                for (String subRuleStr : subRuleArr) {
                    subRuleBoList.addAll(analyzeRule(subRuleStr, isInnerStatement));
                }
                ((MultiRuleBo) ruleBo).setRuleBoList(subRuleBoList);
                ((MultiRuleBo) ruleBo).setOpType(MultiRuleOpType.AND);
            }
            if (str.contains("||")) {
                ruleBo = new MultiRuleBo(str);
                LinkedList<RuleBo> subRuleBoList = new LinkedList<>();
                String[] subRuleArr = str.split("\\|\\|");
                for (String subRuleStr : subRuleArr) {
                    subRuleBoList.addAll(analyzeRule(subRuleStr, isInnerStatement));
//                    ((MultiRuleBo) ruleBo).setRuleBoList(analyzeRule(subRuleStr, isInnerStatement));
                }
                ((MultiRuleBo) ruleBo).setRuleBoList(subRuleBoList);
                ((MultiRuleBo) ruleBo).setOpType(MultiRuleOpType.OR);
            }

            String upperCase = str.toUpperCase(Locale.CHINA);
            if (upperCase.startsWith(RuleType.JS.getName())) {
                ruleBo.setRuleType(RuleType.JS);
            } else if (upperCase.startsWith(RuleType.JSON.getName()) || upperCase.startsWith("$")) {
                ruleBo.setRuleType(RuleType.JSON);
            } else if (upperCase.startsWith(RuleType.XPATH.getName()) || upperCase.startsWith("/")) {
                ruleBo.setRuleType(RuleType.XPATH);
            } else if (upperCase.startsWith(RuleType.CSS.getName())) {
                ruleBo.setRuleType(RuleType.CSS);
            } else if (upperCase.startsWith(RuleType.STR.getName())) {
                ruleBo.setRuleType(RuleType.STR);
            } else {
                if (isInnerStatement) {
                    ruleBo.setRuleType(RuleType.STR);
                } else {
                    ruleBo.setRuleType(RuleType.CSS);
                }
            }
            ruleBoLinkedList.add(ruleBo);
        }
        return ruleBoLinkedList;
    }

    /**
     * 处理动态参数，有时候需要添加，有时候需要使用
     * 添加: {{abc := 123}} {{bcd := '222fff'}}
     * {{}}
     *
     * @return
     */
    private static String processDynamicParams(String url, String doc, RuleBo ruleBo, Map<String, String> varMap) {
        String rule = ruleBo.getContent();
        if (ruleBo.getRuleType() == RuleType.JS && com.whiteape.book.utils.StringUtils.isBase64(rule)) {
            rule = new String(Base64.decode(rule, Base64.DEFAULT));
        }
        Matcher matcher = DYNAMIC_PARAMS.matcher(rule);
        StringBuffer sb = new StringBuffer();
//        if (matcher.find()) {
        while (matcher.find()) {
            String findStr = matcher.group();
            String content = findStr.substring(2, findStr.length() - 2).trim();
            String value = "";
            if (findStr.indexOf(":=") > 0) {
                //put variable
                String[] strArr = content.split(":=");
                //TODO 需要测试是否有问题,继续嵌套问题？
//                RuleBo tempRuleBo = new RuleBo(ruleBo.getRuleType(), strArr[1]);
//                String varValue = getString(url, doc, tempRuleBo, varMap);
                String varValue = parseOne(url, doc, strArr[1], varMap, false);
                varMap.put(strArr[0].trim(), varValue);
            } else {
                //get variable
                //如果是$开头的，那么认定是JSON
                //如果是/开头的，认定是XPATH
                //其他的，认定是STR
                List<RuleBo> ruleBoList = analyzeRule(content, true);
                content = parseOne(url, doc, content, varMap, true);

                //有且只有一个STR规则
                if (ruleBoList.size() == 1 && ruleBoList.get(0).getRuleType() == RuleType.STR) {
                    //只有一个并且还是个str，直接替换
                    if (varMap.containsKey(content)) {
                        value = varMap.get(content);
                    } else {
                        value = content;
                    }
                } else {
                    value = content;
                }
            }
            //不管是添加参数，还是获取参数，最后都将它给替换掉
            //添加参数的{{abc:=123}}形式的，直接用""替换
            //获取参数的{{abc}}用它的值123替换
            matcher.appendReplacement(sb, value);
        }
        matcher.appendTail(sb);
        return sb.toString();
//        }
//        return rule;

    }

    private static String getString(String url, String doc, RuleBo ruleBo, Map<String, String> varMap) {
        //STR格式的，处理完动态参数后，直接就返回了
        String ruleContent = processDynamicParams(url, doc, ruleBo, varMap);
        switch (ruleBo.getRuleType()) {
            case JS:
                return JSEngine.getString(url, doc, ruleContent);
            case JSON:
                return JsonPathEngine.getString(doc, ruleContent);
            case XPATH:
                return XPathEngine.getString(doc, ruleContent);
            case CSS:
                return JsoupSelectorEngine.getString(doc, ruleContent);
            case STR:
                return ruleContent;
        }
        return "";

    }

    private static List getElementList(String url, String doc, RuleBo ruleBo) {

        return getElementList(url, doc, ruleBo, 1, Collections.emptyMap());
    }

    private static List getElementList(String url, String doc, RuleBo ruleBo, int pageTotal, Map<String, String> varMap) {

        String ruleContent = processDynamicParams(url, doc, ruleBo, varMap);
        switch (ruleBo.getRuleType()) {
            case JS:
                return JSEngine.getElementList(url, doc, ruleContent, pageTotal);
            case JSON:
                return JsonPathEngine.getElementList(doc, ruleContent);
            case XPATH:
                return XPathEngine.getElementList(doc, ruleContent);
            case CSS:
                return JsoupSelectorEngine.getElementList(doc, ruleContent);
        }
        return new ArrayList();
    }

    public static List getElementList(String url, String doc, String rule, int pageTotal) {
        return getElementList(url, doc, rule, pageTotal, Collections.emptyMap());
    }

    public static List getElementList(String url, String doc, String rule, int pageTotal, Map<String, String> varMap) {
        try {
            List<RuleBo> ruleBoList = ParserManager.analyzeRule(rule, false);
            if (ruleBoList.isEmpty()) {
                return new ArrayList();
            }
            List result = new ArrayList();
//            RuleBo ruleBo = ruleBoList.get(0);
            //所以前面的需要有 @ 来取数据!!! 坑啊
            int count = 1;
            for (RuleBo ruleBo : ruleBoList) {
                //TODO 这里有个前提，就是有&&和||就不考虑|||了，这是个设计bug
                if (ruleBo instanceof MultiRuleBo) {
                    switch (((MultiRuleBo) ruleBo).getOpType()) {
                        case AND:
                            //对于单个rule而言，不应该还有&&, 只有列表的，才有&&来组合多个
                            for (RuleBo subRuleBo : ((MultiRuleBo) ruleBo).getRuleBoList()) {
                                List cur = getElementList(url, doc, subRuleBo, pageTotal, varMap);
                                if (cur != null && !cur.isEmpty()) {
                                    result.addAll(cur);
                                }
                            }
                            break;
                        case OR:
                            List subResult;
                            for (RuleBo subRuleBo : ((MultiRuleBo) ruleBo).getRuleBoList()) {
                                subResult = getElementList(url, doc, subRuleBo, pageTotal, varMap);
                                if (subResult != null && !subResult.isEmpty()) {
                                    result = subResult;
                                    break;
                                }
                            }
                            break;
                    }
                    //TODO 这里有个前提，就是有&&和||就不考虑|||了，这是个设计bug
                    return result;
                } else {
                    if (count < ruleBoList.size()) {
                        //!!!如果是多个rule先后执行，除了最后一个，前面的都走getString而不是getElementList
                        doc = getString(url, doc, ruleBo, varMap);
                    } else {
                        result = getElementList(url, doc, ruleBo, pageTotal, varMap);
                        doc = getElementListString(result);
                    }
//                    return getElementList(url, doc, ruleBoList.get(0), pageTotal, varMap);
                }
                count ++;
            }
            return result;
        } catch (Exception e) {
            return new ArrayList();
        }
    }


    public static List getElementList(String url, String doc, String rule) {
        return getElementList(url, doc, rule, 1);
    }

    public static String getElementString(Object element) {
        if (element instanceof JXNode) {
            return ((JXNode) element).value().toString();
        } else if (element instanceof NativeObject) {
            //有坑，如果是Array，就不能这么用，会出现当前线程没有上下文的情况
            //但是我们这边现在只有string
            return Context.toString(element);
        } else if (element instanceof Map) {
            return GsonUtils.obj2String(element);
        } else {
            return element.toString();
        }
    }

    private static String getElementListString(List elementList) {
        if (elementList.size() == 0) {
            return "";
        }
        Object object = elementList.get(0);
        if (object instanceof JSONObject) {
            JSONArray jsonArray = new JSONArray();
            for (Object obj : elementList) {
                jsonArray.put(obj);
            }
            return jsonArray.toString();
        }
        //TODO 其他类型的？
        return elementList.toString();

    }
}
